import gtk
from Bases import BaseObject
from devicetree import DeviceTree
from bases import widgets, listmodel

class EditorBase(BaseObject):
    def __init__(self, **kwargs):
        super(EditorBase, self).__init__(**kwargs)
        self.child_editors = {}
        self.MainController = kwargs.get('MainController')
        self.DeviceSystem = self.MainController.DeviceSystem
        frame = widgets.Frame(label=self.__class__.topwidget_label)
        self.topwidget = kwargs.get('topwidget', frame)
        self.init_editor(**kwargs)
        self.topwidget.show_all()
    def make_child_editor(self, cls, name, **kwargs):
        kwargs.setdefault('MainController', self.MainController)
        obj = cls(**kwargs)
        self.child_editors.update({name:obj})
        return obj
    def unlink(self):
        for child in self.child_editors.itervalues():
            child.unlink()
        
class DeviceEditor(EditorBase):
    topwidget_label = 'Device'
    def init_editor(self, **kwargs):
        self.device = kwargs.get('device')
        self.attribute_editor = None
        
        hbox = widgets.HBox()
        vbox = widgets.VBox()
        keys = ('Name', 'Manufacturer', 'Description', 'Personality Mode')
        attrs = ['name', 'manufacturer', 'description', 'personality_mode']
        self.device_prop_attr_map = dict(zip(keys, attrs))
        self.device_prop_keys = keys
        names = ['Property', 'Value']
        types = [str, str]
        editable = [1]
        order = [0, 1]
        self.device_prop_list = widgets.TreeList(name='Device Properties', column_names=names, 
                                                 list_types=types, column_order=order, 
                                                 columns_editable=editable)
        self.update_device_prop_list()
        self.device_prop_list.connect('cell_edited', self.on_device_prop_list_cell_edited)
        vbox.pack_start(self.device_prop_list.topwidget, expand=True)
        self.sbPanTiltRange = {}
        if 'Mover' in [cat.name for cat in self.device.categories.values()]:
            poskwargs = {'value_max':999}
            for key in ['pan_range', 'tilt_range']:
                if self.device.saved_class_name == 'DefinedDevice':
                    obj = self.device
                else:
                    obj = self.device.Groups['Position']
                poskwargs['Property'] = obj.Properties[key]
                poskwargs['label'] = ' '.join(key.split('_')).capitalize()
                self.sbPanTiltRange[key] = widgets.SpinBtn(**poskwargs)
                vbox.pack_start(self.sbPanTiltRange[key].topwidget)
        
        self.sbChan = widgets.SpinBtn(label='Channel Count', Property=(self.device, 'channel_count'))
        vbox.pack_start(self.sbChan.topwidget)
        
        hbox.pack_start(vbox, expand=True)
        
        obj = self.make_child_editor(CategoryEditor, 'categories', src_object=self.device)
        hbox.pack_start(obj.topwidget, expand=True)
        
        self.topwidget.pack_start(hbox, expand=True)
        
        box = widgets.HBox()
        self.column_attr_map = {'device':{0:'id', 1:'name', 2:'chan_index'}, 
                                'global':{0:'id', 1:'name', 2:'name'}}
        names = ['id', 'Name', 'Type']
        types = [str, str, str]
        order = [1, 2]
        
        self.global_attribute_list = widgets.TreeList(name='Global Attributes', column_names=names, 
                                                      list_types=types, column_order=order)
        self.update_global_attribute_list()
        self.global_attribute_list.connect('selection_changed', self.on_global_attribute_list_selection_changed)
        hbox2 = widgets.HBox()
        self.btn_add_attribute = widgets.Button(label='Add Attribute')
        self.btn_add_attribute.connect('clicked', self.on_btn_add_attribute_clicked)
        hbox2.pack_start(self.btn_add_attribute)
        self.btn_del_attribute = widgets.Button(label='Delete Attribute')
        self.btn_del_attribute.connect('clicked', self.on_btn_del_attribute_clicked)
        hbox2.pack_start(self.btn_del_attribute)
        self.global_attribute_list.topwidget.pack_start(hbox2)
        box.pack_start(self.global_attribute_list.topwidget, expand=True)
        
        names = ['id', 'Name', 'Channel']
        types = [str, str, int]
        order = [1, 2]
        editable = [1, 2]
        
        self.device_attribute_list = widgets.TreeList(name='Device Attributes', column_names=names, default_sort_column=2, 
                                               list_types=types, column_order=order, columns_editable=editable)
        self.update_device_attribute_list()
        self.device_attribute_list.connect('selection_changed', self.on_device_attribute_list_selection_changed)
        self.device_attribute_list.connect('cell_edited', self.on_device_attribute_list_cell_edited)
        box.pack_start(self.device_attribute_list.topwidget, expand=True)
        
        self.topwidget.pack_start(box, expand=True)
        self.hbox = box
        
        self.DeviceSystem.bind(**dict(zip(['new_attribute', 'del_attribute'], [self.on_GlobalAttributes_child_update]*2)))
        self.device.Attributes.bind(**dict(zip(['child_added', 'child_removed'], [self.on_Attributes_child_update]*2)))
        self.device.bind(property_changed=self.on_device_property_changed)
        #for attr in self.device_prop_attr_map.itervalues():
        #    self.device.connect('attr_watch_%s' % (attr), self.on_device_property_changed)
        
        
    def unlink(self):
        super(DeviceEditor, self).unlink()
        self.DeviceSystem.unbind(self.on_GlobalAttributes_child_update)
        for attrib in self.DeviceSystem.GlobalAttributes.itervalues():
            attrib.unbind(self.on_global_attribute_property_changed)
        for attrib in self.device.Attributes.itervalues():
            attrib.unbind(self.on_device_attribute_property_changed)
        self.sbChan.unlink()
        for sb in self.sbPanTiltRange.itervalues():
            sb.unlink()
        self.device.unbind(self.on_device_property_changed)
        self.device.Attributes.unbind(self.on_Attributes_child_update)
        
        
    def update_device_prop_list(self, key=None):
        if key:
            attr = self.device_prop_attr_map[key]
            self.device_prop_list.update({key:getattr(self.device, attr)})
        else:
            for key in self.device_prop_keys:
                attr = self.device_prop_attr_map[key]
                self.device_prop_list.update({key:getattr(self.device, attr)})
    
    def update_global_attribute_list(self, key=None):
        if key is not None:
            attributes = {key:self.DeviceSystem.GlobalAttributes[key]}
            #obj = self.DeviceSystem.GlobalAttributes[key]
            #values = [getattr(obj, self.column_attr_map['global'][x]) for x in [1, 2]]
            #self.global_attribute_list.update({key:values})
        else:
            attributes = self.DeviceSystem.GlobalAttributes
        for key, val in attributes.iteritems():
            values = [getattr(val, self.column_attr_map['global'][x]) for x in [1, 2]]
            self.global_attribute_list.update({key:values})
            val.bind(property_changed=self.on_global_attribute_property_changed)
                
    def update_device_attribute_list(self, key=None):
        if key is not None:
            attributes = {key:self.device.Attributes[key]}
            #obj = self.device.Attributes[key]
            #values = [getattr(obj, self.column_attr_map['device'][x]) for x in [1, 2]]
            #self.device_attribute_list.update({key:values})
        else:
            attributes = self.device.Attributes
        for key, val in attributes.iteritems():
            values = [getattr(val, self.column_attr_map['device'][x]) for x in [1, 2]]
            self.device_attribute_list.update({key:values})
            val.bind(property_changed=self.on_device_attribute_property_changed)
                
    def on_device_prop_list_cell_edited(self, **kwargs):
        key = kwargs.get('key')
        value = kwargs.get('value')
        attr = self.device_prop_attr_map[key]
        setattr(self.device, attr, value)
    
    def on_device_attribute_list_cell_edited(self, **kwargs):
        key = kwargs.get('key')
        column = kwargs.get('column')
        value = kwargs.get('value')
        attr = self.column_attr_map['device'][column]
        setattr(self.device.Attributes[key], attr, value)
                
    def on_global_attribute_list_selection_changed(self, **kwargs):
        pass
        
    def on_device_attribute_list_selection_changed(self, **kwargs):
        key = kwargs.get('key')
        if self.attribute_editor is not None:
            self.attribute_editor.unlink()
            self.hbox.remove(self.attribute_editor.topwidget)
            self.attribute_editor = None
        attribute = self.device.Attributes.get(key)
        if attribute:
            obj = self.make_child_editor(AttributeEditor, 'attribute', attribute=attribute)
            self.hbox.pack_start(obj.topwidget, expand=True)
            self.attribute_editor = obj
        
    def on_btn_add_attribute_clicked(self, btn):
        key = self.global_attribute_list.current_selection
        attr = self.DeviceSystem.GlobalAttributes[key]
        if attr is not None:
            dev_attr = self.device.add_attribute(attribute=attr)
            self.update_device_attribute_list(dev_attr.id)
        
    def on_btn_del_attribute_clicked(self, btn):
        key = self.device_attribute_list.current_selection
        if key:
            self.device.del_attribute(id=key)
            #self.device_attribute_list.clear()
            #self.update_device_attribute_list()
            
    def on_GlobalAttributes_child_update(self, **kwargs):
        key = kwargs.get('id')
        if kwargs.get('type') != 'GlobalAttribute':
            return
        if kwargs.get('signal_name') == 'del_attribute':
            self.global_attribute_list.clear()
            key = None
        self.update_global_attribute_list(key)
        
    def on_Attributes_child_update(self, **kwargs):
        key = kwargs.get('obj').id
        if kwargs.get('signal_name') == 'child_removed':
            self.device_attribute_list.clear()
            key = None
        self.update_device_attribute_list(key)
        
    def on_device_property_changed(self, **kwargs):
        name = kwargs.get('name')
        for key, val in self.device_prop_attr_map.iteritems():
            if val in name:
                self.update_device_prop_list(key)
        
    def on_global_attribute_property_changed(self, **kwargs):
        attrib = kwargs.get('obj')
        self.update_global_attribute_list(attrib.id)
        
    def on_device_attribute_property_changed(self, **kwargs):
        attrib = kwargs.get('obj')
        self.update_device_attribute_list(attrib.id)

class PatchedDeviceEditor(EditorBase):
    topwidget_label = 'Device'
    _Properties = {'device_number':dict(type=int)}
    def init_editor(self, **kwargs):
        self.device = kwargs.get('device')
        self.device_number = self.device.device_num
        self._start_chan = self.device.start_chan
        vbox = widgets.VBox()
        ## TODO: make it find properties in the attribute groups
        keys = ('Name', 'Description', 'Pan Invert', 'Tilt Invert')
        attrs = ['name', 'description', 'pan_invert', 'tilt_invert']
        self.device_prop_attr_map = dict(zip(keys, attrs))
        self.device_prop_keys = keys
        names = ['Property', 'Value', 'Active', 'cell_active']
        types = [str, str, bool, bool]
        editable = [1, 2]
        order = [0, 1, 2]
        self.device_prop_list = widgets.TreeList(name='Device Properties', column_names=names, 
                                                 list_types=types, column_order=order, 
                                                 columns_editable=editable)
        self.update_device_prop_list()
        self.device_prop_list.connect('cell_edited', self.on_device_prop_list_cell_edited)
        self.device_prop_list.connect('cell_toggled', self.on_device_prop_list_cell_toggled)
        vbox.pack_start(self.device_prop_list.topwidget, expand=True)
        
        hbox = widgets.HBox()
        self.sbDevNum = widgets.SpinBtn(label='Device Number', Property=(self, 'device_number'))
        hbox.pack_start(self.sbDevNum.topwidget)
        btn = widgets.Button(label='Update')
        btn.connect('clicked', self.on_btn_device_number_update_clicked)
        hbox.pack_start(btn)
        vbox.pack_start(hbox)
        
        hbox = widgets.HBox()
        self.universe_combo = UniverseCombo(MainController=self.MainController)
        self.universe_combo.set_current_selection(key=self.device.universe_id)
        self.universe_combo.connect('selection_changed', self.on_universe_combo_selection_changed)
        hbox.pack_start(self.universe_combo.topwidget)
        self.sbStartChan = widgets.SpinBtn(label='Start Channel', src_object=self, 
                                           src_attr='start_chan', value_max=512)
        hbox.pack_start(self.sbStartChan.topwidget)
        btn = widgets.Button(label='Repatch')
        btn.connect('clicked', self.on_btn_repatch_clicked)
        hbox.pack_start(btn)
        vbox.pack_start(hbox)
        
        self.topwidget.pack_start(vbox, expand=True)
        
        self.device.bind(property_changed=self.on_device_property_changed)
        #for attr in self.device_prop_attr_map.itervalues():
        #    self.device.connect('attr_watch_%s' % (attr), self.on_device_property_changed)
        
    def unlink(self):
        super(PatchedDeviceEditor, self).unlink()
        self.device.unbind(self.on_device_property_changed)
        
    @property
    def start_chan(self):
        return self._start_chan
    @start_chan.setter
    def start_chan(self, value):
        self._start_chan = int(value)
        
    def update_device_prop_list(self, key=None):
        if key:
            props = [key]
        else:
            props = self.device_prop_attr_map
        for key in props:
            attr = self.device_prop_attr_map[key]
            value = getattr(self.device, attr)
            if type(value) == bool:
                row = ['', value, True]
            else:
                row = [value, False, False]
            self.device_prop_list.update({key:row})
    
    def on_btn_device_number_update_clicked(self, btn):
        self.device.device_num = self.device_number
        self._device_number = self.device.device_num
        self.sbDevNum.on_Property_value_changed()
    
    def on_device_prop_list_cell_edited(self, **kwargs):
        key = kwargs.get('key')
        value = kwargs.get('value')
        attr = self.device_prop_attr_map[key]
        setattr(self.device, attr, value)
        
    def on_device_prop_list_cell_toggled(self, **kwargs):
        key = kwargs.get('key')
        state = kwargs.get('state')
        attr = self.device_prop_attr_map[key]
        setattr(self.device, attr, state)
        
    def on_device_property_changed(self, **kwargs):
        prop = kwargs.get('Property')
        if prop.name == 'Index':
            self.device_number = prop.value
        for key, val in self.device_prop_attr_map.iteritems():
            if val in prop.name:
                self.update_device_prop_list(key)
            
    def on_universe_combo_selection_changed(self, **kwargs):
        pass
    
    def on_btn_repatch_clicked(self, btn):
        kwargs = dict(universe_id=self.universe_combo.current_selection, 
                      start_chan=self.start_chan, device=self.device)
        if None not in kwargs.values():
            self.DeviceSystem.repatch_device(**kwargs)
    
class UniverseCombo(widgets.Combo):
    def __init__(self, **kwargs):
        kwargs['list_types'] = [str, str]
        kwargs['name'] = 'Universes'
        super(UniverseCombo, self).__init__(**kwargs)
        self.MainController = kwargs.get('MainController')
        self.DeviceSystem = self.MainController.DeviceSystem
        for key, val in self.DeviceSystem.universes.iteritems():
            self.update({key:val.name})
        keys = self.DeviceSystem.universes.keys()
        if len(keys) == 0:
            univ_id = None
        else:
            univ_id = keys[0]
        self.set_current_selection(key=univ_id)

class PatchEditor(EditorBase):
    topwidget_label = 'Patch Editor'
    _Properties = {'patch_count':dict(default=1, min=1, max=512), 
                   'start_chan':dict(default=1, min=1, max=512)}
    def init_editor(self, **kwargs):
        self.definition = kwargs.get('definition')
        self.patch_grid = None
        self._start_chan = 1
        self._patch_count = 1
        hbox = widgets.HBox()
        
        self.patch_grid_container = widgets.ScrolledWindow()
        self.topwidget.pack_start(self.patch_grid_container, expand=True)
        
        self.universe_combo = UniverseCombo(MainController=self.MainController)
        self.universe_combo.connect('selection_changed', self.on_universe_combo_selection_changed)
        
        self.on_universe_combo_selection_changed()
        
        hbox.pack_start(self.universe_combo.topwidget)
        
        self.sbPatchCount = widgets.SpinBtn(label='Device Count', Property=(self, 'patch_count'))
        hbox.pack_start(self.sbPatchCount.topwidget)
        self.sbStartChan = widgets.SpinBtn(label='Start Channel', Property=(self, 'start_chan'))
        btn = widgets.Button(label='Find')
        btn.connect('clicked', self.on_btnFindStartChan_clicked)
        self.sbStartChan.topwidget.pack_start(btn)
        hbox.pack_start(self.sbStartChan.topwidget)
        
        self.topwidget.pack_start(hbox)
        
        self.btn_patch = widgets.Button(label='Patch')
        self.btn_patch.connect('clicked', self.on_btn_patch_clicked)
        self.topwidget.pack_start(self.btn_patch)
        
        self.topwidget.show_all()
            
    def make_patch_grid(self, universe):
        if self.patch_grid:
            self.patch_grid.unlink()
            w = self.patch_grid.topwidget
            w.get_parent().remove(w)
        self.patch_grid = self.make_child_editor(UniversePatchEditor, 'patch_grid', universe=universe)
        self.patch_grid_container.pack_start(self.patch_grid.topwidget)
            
    def on_universe_combo_selection_changed(self, **kwargs):
        key = kwargs.get('key')
        if key is None:
            key = self.universe_combo.current_selection
        self.make_patch_grid(self.DeviceSystem.universes[key])
    
    def on_btnFindStartChan_clicked(self, btn):
        univ_id = self.universe_combo.current_selection
        kwargs = dict(device=self.definition, universe_id=univ_id, count=self.patch_count)
        if univ_id is not None:
            start = self.DeviceSystem.find_first_start_chan(**kwargs)
            if start is False:
                start = 0
            self.start_chan = start
        
    def on_btn_patch_clicked(self, btn):
        univ_id = self.universe_combo.current_selection
        if univ_id is not None:
            device = self.DeviceSystem.patch_device(self.definition, 
                                                    universe_id=univ_id, 
                                                    start_chan=self.start_chan, 
                                                    count=self.patch_count)
        
class AttributeEditor(EditorBase):
    topwidget_label = 'Attribute'
    def init_editor(self, **kwargs):
        self.attribute = kwargs.get('attribute')
        
        self.txtName = widgets.Entry(label='Name', Property=(self.attribute, 'name'))
        #self.txtName = widgets.Text(label='Name', src_object=self.attribute, 
        #                            src_attr='name', allow_obj_setattr=True)
        self.topwidget.pack_start(self.txtName.topwidget)
        
        if self.attribute.saved_class_name in ['DefinitionAttribute', 'PatchedAttribute']:
            self.sbChan = widgets.SpinBtn(label='Channel', Property=(self.attribute, 'Index'))
            self.topwidget.pack_start(self.sbChan.topwidget)
            self.cbSixteenBit = widgets.CheckBox(label='16 bit mode')
            self.cbSixteenBit.state = self.attribute.sixteen_bit
            self.cbSixteenBit.connect('clicked', self.on_cbSixteenBit_clicked)
            self.topwidget.pack_start(self.cbSixteenBit)
            #self.attribute.connect('chan_index_changed', self.sbChan.on_object_value_changed)
            self.attribute.bind(sixteen_bit=self.on_sixteen_bit_set)
        else:
            self.sbChan = None
            obj = self.make_child_editor(CategoryEditor, 'categories', src_object=self.attribute)
            self.topwidget.pack_start(obj.topwidget)
        
        self.sbInitVal = widgets.SpinBtn(label='Initial Value', Property=(self.attribute, 'initial_value'))
        self.topwidget.pack_start(self.sbInitVal.topwidget)
        
        obj = self.make_child_editor(ParameterEditor, 'parameters', attribute=self.attribute)
        self.topwidget.pack_start(obj.topwidget, expand=True)
        
        #self.attribute.connect('attr_watch_name', self.txtName.on_object_value_changed)
        
    def unlink(self):
        super(AttributeEditor, self).unlink()
        self.txtName.unlink()
        self.sbInitVal.unlink()
        self.attribute.unbind(self.on_sixteen_bit_set)
        if self.sbChan is not None:
            self.sbChan.unlink()
        
    def on_cbSixteenBit_clicked(self, cb):
        if self.attribute.sixteen_bit != cb.state:
            self.attribute.sixteen_bit = cb.state
        
    def on_sixteen_bit_set(self, **kwargs):
        value = kwargs.get('value')
        if value != self.cbSixteenBit.state:
            self.cbSixteenBit.state = value
        
class CategoryEditor(EditorBase):
    topwidget_label = 'Categories'
    def init_editor(self, **kwargs):
        self.src_object = kwargs.get('src_object')
        self.category_type = None
        self.category_dict = self.DeviceSystem.Categories
        if 'attribute' in self.src_object.saved_class_name.lower():
            self.category_type = 'attribute'
            self.category_dict = self.DeviceSystem.Categories['attribute']
        elif 'device' in self.src_object.saved_class_name.lower():
            self.category_type = 'device'
            self.category_dict = self.DeviceSystem.Categories['device']
            
        self.list = widgets.TreeList(name='Categories', list_types=[str, str, bool, bool], 
                                     column_names=['id', 'Name', 'Enabled', 'cell_active'], 
                                     column_order=[1, 2], columns_editable=[1])
        self.update_list()
        self.list.connect('cell_toggled', self.on_cell_toggled)
        self.list.connect('cell_edited', self.on_cell_edited)
        self.btn_add_category = widgets.Button(label='Add Category')
        self.btn_add_category.connect('clicked', self.on_btn_add_category_clicked)
        self.list.topwidget.pack_start(self.btn_add_category)
        self.topwidget = self.list.topwidget
        
        self.DeviceSystem.bind(new_category=self.on_new_category)
        if self.src_object is not None:
            self.src_object.bind(category_update=self.on_category_update)
        
    def unlink(self):
        super(CategoryEditor, self).unlink()
        if self.src_object is not None:
            self.src_object.unbind(self.on_category_update)
            self.DeviceSystem.unbind(self.on_new_category)
            
    def update_list(self, key=None):
        if key:
            category = self.category_dict.get(key)
            self.list.update({key:[category.name, self.src_object in category.members, True]})
        else:
            for key, val in self.category_dict.iteritems():
                self.list.update({key:[val.name, self.src_object in val.members, True]})
        
    def on_btn_add_category_clicked(self, btn):
        self.DeviceSystem.add_category(type=self.category_type, name='New Category')
        
    def on_category_update(self, **kwargs):
        category = kwargs.get('category')
        state = kwargs.get('state')
        self.update_list(category.id)
        
    def on_new_category(self, **kwargs):
        category = kwargs.get('category')
        if kwargs.get('type') == self.category_type:
            self.update_list(category.id)
        
    def on_cell_toggled(self, **kwargs):
        key = kwargs.get('key')
        state = kwargs.get('state')
        category = self.category_dict.get(key)
        if state:
            self.src_object.add_category(category)
        else:
            self.src_object.remove_category(category)
            
    def on_cell_edited(self, **kwargs):
        key = kwargs.get('key')
        column = kwargs.get('column')
        value = kwargs.get('value')
        if column == 1:
            self.category_dict[key].name = value
            self.update_list(key)
            

class ParameterEditor(EditorBase):
    topwidget_label = 'Parameters'
    def init_editor(self, **kwargs):
        self.attribute = kwargs.get('attribute')
        
        if self.attribute.saved_class_name == 'GlobalAttribute':
            self.global_param_list = widgets.TreeList(name='Parameters', column_names=['id', 'Name'], 
                                                      column_order=[1], columns_editable=[1])
            self.global_param_list.connect('cell_edited', self.on_global_list_cell_edited)
            self.global_param_source = self.attribute
            btn = widgets.Button(label='Add Parameter')
            btn.connect('clicked', self.on_btnAddGlobalParam_clicked)
            self.global_param_list.topwidget.pack_start(btn)
            self.topwidget = self.global_param_list.topwidget
            
        elif self.attribute.saved_class_name == 'DefinitionAttribute':
            d = dict(name='Parameters', column_names=['id', 'Name', 'Range Lo', 'Range Hi'], default_sort_column=2, 
                     column_order=[1, 2, 3], columns_editable=[2, 3], list_types=[str, str, int, int])
            self.definition_param_list = widgets.TreeList(**d)
            self.definition_param_list.connect('cell_edited', self.on_definition_list_cell_edited)
            
            self.update_definition_param_list()
            self.global_param_list = widgets.Combo(name='Add Parameter', list_types=[str, str])
            self.global_param_source = self.attribute.GlobalAttribute
            
            btn = widgets.Button(label='Add')
            btn.connect('clicked', self.on_btnAddDefinitionParam_clicked)
            self.global_param_list.topwidget.pack_start(btn)
            btn = widgets.Button(label='Delete')
            btn.connect('clicked', self.on_btnDelDefinitionParam_clicked)
            self.definition_param_list.topwidget.pack_start(btn)
            
            self.definition_param_list.topwidget.pack_start(self.global_param_list.topwidget, expand=True)
            self.attribute.connect('new_indexed_parameter', self.update_definition_param_list)
            self.topwidget = self.definition_param_list.topwidget
            
        self.update_global_param_list()
        self.global_param_source.connect('new_indexed_parameter', self.update_global_param_list)
        
    def unlink(self):
        super(ParameterEditor, self).unlink()
        self.global_param_source.disconnect(callback=self.update_global_param_list)
        self.attribute.disconnect(callback=self.update_definition_param_list)
            
    def update_global_param_list(self, **kwargs):
        param = kwargs.get('parameter')
        key = kwargs.get('key')
        if key and not param:
            param = self.global_param_source.indexed_parameters.get(key)
        if param:
            self.global_param_list.update({param.id:param.name})
        else:
            for key, val in self.global_param_source.indexed_parameters.iteritems():
                self.global_param_list.update({key:val.name})
        
    def update_definition_param_list(self, **kwargs):
        param = kwargs.get('parameter')
        key = kwargs.get('key')
        if key and not param:
            param = self.attribute.indexed_parameters.get(key)
        if param:
            values = [getattr(param, attr) for attr in ['name', 'value_min', 'value_max']]
            self.definition_param_list.update({param.id:values})
        else:
            for key, val in self.attribute.indexed_parameters.iteritems():
                values = [getattr(val, attr) for attr in ['name', 'value_min', 'value_max']]
                self.definition_param_list.update({key:values})
    
    def on_btnAddGlobalParam_clicked(self, btn):
        self.attribute.add_indexed_parameter(name='New Parameter')
    
    def on_btnAddDefinitionParam_clicked(self, btn):
        gkey = self.global_param_list.current_selection
        if gkey:
            self.attribute.add_indexed_parameter(global_parameter_id=gkey)
        
    def on_btnDelDefinitionParam_clicked(self, btn):
        key = self.definition_param_list.current_selection
        if key:
            self.attribute.del_indexed_parameter(id=key)
            self.definition_param_list.clear()
            self.update_definition_param_list()
        
    def on_global_list_cell_edited(self, **kwargs):
        key = kwargs.get('key')
        column = kwargs.get('column')
        value = kwargs.get('value')
        self.global_param_source.indexed_parameters[key].name = value
        self.update_global_param_list(key=key)
        
    def on_definition_list_cell_edited(self, **kwargs):
        key = kwargs.get('key')
        column = kwargs.get('column')
        value = kwargs.get('value')
        d = {2:'value_min', 3:'value_max'}
        setattr(self.attribute.indexed_parameters[key], d[column], value)
        self.update_definition_param_list(key=key)

class UniverseEditor(EditorBase):
    topwidget_label = 'Universes'
    def init_editor(self, **kwargs):
        self.universe = kwargs.get('universe')
        self.txtName = widgets.Entry(name='Name', Property=(self.universe, 'name'))
        self.topwidget.pack_start(self.txtName.topwidget)
        self.sbOLAUnivID = widgets.SpinBtn(name='OLA Universe ID', Property=(self.universe, 'ola_universe_id'))
        self.topwidget.pack_start(self.sbOLAUnivID.topwidget)
        #self.patch_editor = self.make_child_editor(UniversePatchEditor, 'patch', universe=self.universe)
        #self.topwidget.pack_start(self.patch_editor.topwidget, expand=True)
        self.value_editor = self.make_child_editor(UniverseValueEditor, 'values', universe=self.universe)
        self.topwidget.pack_start(self.value_editor.topwidget, expand=True)
    def unlink(self):
        super(UniverseEditor, self).unlink()
        self.txtName.unlink()
        self.sbOLAUnivID.unlink()
        
class UniversePatchEditor(EditorBase):
    topwidget_label = 'Patch View'
    def init_editor(self, **kwargs):
        self.universe = kwargs.get('universe')
        self.column_count = kwargs.get('column_count', 32)
        self.row_count = 512 / self.column_count
        self.column_types = [bool] * (self.column_count + 1)
        self.list = gtk.ListStore(*self.column_types)
        self.widget = gtk.TreeView(model=self.list)
        for x in range(self.column_count):
            cell = gtk.CellRendererToggle()
            col = gtk.TreeViewColumn(str(x+1), cell, active=x, activatable=self.column_count + 1)
            self.widget.append_column(col)
        self.topwidget.pack_start(self.widget, expand=True)
        self.update_list()
        self.DeviceSystem.connect('new_device', self.update_list)
        self.DeviceSystem.connect('patch_change', self.update_list)
    def unlink(self):
        super(UniversePatchEditor, self).unlink()
        self.DeviceSystem.disconnect(callback=self.update_list)
    def update_list(self, **kwargs):
        self.list.clear()
        chan = 0
        for y in range(self.row_count):
            row = []
            for x in range(self.column_count):
                chan += 1
                row.append(chan in self.DeviceSystem.patch_by_chan[self.universe.id])
            row.append(False)
            self.list.append(row)
        
class UniverseValueEditor(EditorBase):
    topwidget_label = 'Universe Values'
    def init_editor(self, **kwargs):
        self.universe = kwargs.get('universe')
        self.column_count = kwargs.get('column_count', 32)
        self.row_count = 512 / self.column_count
        self.column_types = [int] * self.column_count
        self.list = gtk.ListStore(*self.column_types)
        self.iters = []
        self.map = []
        for row in range(self.row_count):
            for col in range(self.column_count):
                self.map.append((row, col))
        self.widget = gtk.TreeView(model=self.list)
        for x in range(self.column_count):
            cell = gtk.CellRendererText()
            col = gtk.TreeViewColumn(str(x+1), cell, text=x)
            self.widget.append_column(col)
        self.topwidget.pack_start(self.widget, expand=True)
        self.update_list()
        self.universe.connect('value_update', self.on_universe_value_update)
    def unlink(self):
        self.universe.disconnect(callback=self.on_universe_value_update)
        super(UniverseValueEditor, self).unlink()
    def update_list(self, index=None):
        if index is not None:
            key = self.map[index]
            self.list.set_value(self.iters[key[0]], key[1], self.universe.values[index])
        else:
            for row in range(self.row_count):
                start = self.map.index((row, 0))
                end = start + self.column_count
                row = self.universe.values[start:end]
                self.iters.append(self.list.append(row))
    def on_universe_value_update(self, **kwargs):
        self.update_list(kwargs.get('channel')-1)

class CueStackEditor(EditorBase):
    topwidget_label = 'Cue Stacks'
    _Properties = {'cue_index':dict(default=0, min=0, max=9999), 
                   'next_index':dict(default=0, min=0, max=9999)}
    def init_editor(self, **kwargs):
        self.current_cue_members = set()
        self.stack = kwargs.get('stack')
        
        frame = widgets.Frame(label='Cue Stack Properties')
        self.txtStackName = widgets.Entry(name='Name', Property=(self.stack, 'name'))
        frame.pack_start(self.txtStackName.topwidget)
        self.sbStackIndex = widgets.SpinBtn(label='Stack Index', Property=(self.stack, 'Index'))
        frame.pack_start(self.sbStackIndex.topwidget)
        btn = widgets.Button(label='Add a Playback for this Stack')
        btn.connect('clicked', self.on_btnAddPlayback_clicked)
        frame.pack_start(btn)   
        self.playback_fader = None
   
        self.topwidget.pack_start(frame)
        
        i = self.stack.current_index
        if not i:
            i = 1
        self.cue_index = i
        self.next_index = i
        hbox = widgets.HBox()
        
        list_types = [str, int, str, float]
        col_names = ['id', 'Index', 'Name', 'Fade Time']
        col_order = [1, 2, 3]
        columns_editable = [1, 2, 3]
        self.column_attr_map = {0:'id', 1:'Index', 2:'name', 3:'fade_time'}
        self.list = widgets.TreeList(name='Cues', list_types=list_types, default_sort_column=1, 
                                     column_names=col_names, column_order=col_order, 
                                     columns_editable=columns_editable)
        self.update_list()
        self.list.connect('cell_edited', self.on_cell_edited)
        self.list.connect('selection_changed', self.on_list_selection_changed)
        
        if self.stack.playback:
            self.add_playback_controls()
        
        self.spCueIndex = widgets.SpinBtn(label='Current', Property=(self, 'cue_index'), no_frame=True)
        self.spNextIndex = widgets.SpinBtn(label='Next', Property=(self, 'next_index'), no_frame=True)
        self.btn_record = widgets.Button(label='Record Cue')
        self.btn_record.connect('clicked', self.on_btn_record_clicked)
        self.btn_recall = widgets.Button(label='Recall')
        self.btn_recall.connect('clicked', self.on_btn_recall_clicked)
        
        hbox2 = widgets.HBox()
        hbox2.pack_start(self.spCueIndex.topwidget)
        hbox2.pack_start(self.spNextIndex.topwidget)
        
        hbox3 = widgets.HBox()
        hbox3.pack_start(self.btn_record)
        hbox3.pack_start(self.btn_recall)
        
        self.list.topwidget.pack_start(hbox2)
        self.list.topwidget.pack_start(hbox3)
        
        hbox.pack_start(self.list.topwidget, expand=True)
        
        self.topwidget.pack_start(hbox, expand=True)
        
        
        self.group_list = widgets.TreeList(name='Group Selection', 
                                           column_names=['id', 'Name', 'Selected', 'active'], 
                                           list_types=[str, str, bool, bool], 
                                           column_order=[1, 2], 
                                           columns_editable=[2])
        self.update_group_list()
        self.group_list.connect('cell_toggled', self.on_group_list_cell_toggled)
        self.topwidget.pack_start(self.group_list.topwidget, expand=True)
        
        self.stack.connect('new_snapshot', self.on_new_snapshot)
        self.stack.connect('current_index_changed', self.on_stack_current_index_changed)
        self.MainController.connect('new_group', self.on_mc_new_group)
        
    def unlink(self):
        super(CueStackEditor, self).unlink()
        self.stack.disconnect(callback=self.on_new_snapshot)
        self.MainController.disconnect(callback=self.on_mc_new_group)
        self.txtStackName.unlink()
        self.sbStackIndex.unlink()
        if self.playback_fader:
            self.playback_fader.unlink()
            w = self.playback_fader.topwidget
            w.get_parent().remove(w)
        
#    @property
#    def current_cue_members(self):
#        key = self.list.current_selection
#        if key is None:
#            return []
#        snapshot = self.stack.Snapshots[key]
#        return snapshot.root_members
    
    def update_list(self, key=None):
        if key is not None:
            snapshots = {key:self.stack.Snapshots[key]}
        else:
            snapshots = self.stack.Snapshots
        for key, val in snapshots.iteritems():
            values = [getattr(val, self.column_attr_map[x]) for x in [1, 2, 3]]
            self.list.update({key:values})
                
    def update_group_list(self, key=None):
        if key is not None:
            groups = {key:self.MainController.Groups[key]}
        else:
            groups = self.MainController.Groups
        for key, val in groups.iteritems():
            self.group_list.update({key:[val.name, key in self.current_cue_members, True]})
    
    def on_new_snapshot(self, **kwargs):
        key = kwargs.get('id')
        self.update_list(key)
        
    def on_stack_current_index_changed(self, **kwargs):
        current = kwargs.get('value')
        next = kwargs.get('next')
        self.cue_index = current
        self.next_index = next
        #self.spCueIndex.on_object_value_changed()
        #self.spNextIndex.on_object_value_changed()
    
    def add_playback_controls(self):
        pb = self.stack.playback
        self.playback_fader = widgets.VSlider(Property=(pb, 'value'))
        w = self.list.topwidget.get_parent()
        w.pack_start(self.playback_fader.topwidget, expand=True)
        self.playback_fader.topwidget.show_all()
        
    def on_btnAddPlayback_clicked(self, btn):
        self.MainController.add_playback(stack=self.stack)
        self.add_playback_controls()
    
    def on_btn_record_clicked(self, btn):
        # TODO: change the source to selected after MainController selection implemented
        groups = [self.MainController.Groups[key] for key in self.current_cue_members]
        self.MainController.store_to_CueStack(stack=self.stack, index=int(self.cue_index), source=groups)
        #print self.MainController.Selection.members.values()
        
    def on_btn_recall_clicked(self, btn):
        if self.cue_index in self.stack.Snapshots.indexed_items:
            self.stack.recall_snapshot(index=self.next_index)
    
    def on_cell_edited(self, **kwargs):
        key = kwargs.get('key')
        column = kwargs.get('column')
        value = kwargs.get('value')
        attr = self.column_attr_map[column]
        setattr(self.stack.Snapshots[key], attr, value)
        self.update_list()
    
    def on_list_selection_changed(self, **kwargs):
        key = kwargs.get('key')
        snapshot = self.stack.Snapshots[key]
        self.cue_index = snapshot.Index
        self.next_index = snapshot.Index
        #self.spCueIndex.on_object_value_changed()
        #self.spNextIndex.on_object_value_changed()
        self.update_group_list()
        if snapshot.members:
            #self.current_cue_members.clear()
            self.current_cue_members |= snapshot.root_members
            self.update_group_list()
        
    def on_group_list_cell_toggled(self, **kwargs):
        grkey = kwargs.get('key')
        if kwargs.get('state'):
            self.current_cue_members.add(grkey)
        else:
            self.current_cue_members.discard(grkey)
        self.update_group_list(grkey)
        
    def on_mc_new_group(self, **kwargs):
        key = kwargs.get('id')
        self.update_group_list(key)
        

class GroupEditor(EditorBase):
    topwidget_label = 'Group Properties'
    def init_editor(self, **kwargs):
        self.group = kwargs.get('group')
        vbox = widgets.VBox()
        
        hbox1 = widgets.HBox()
        self.txtName = widgets.Entry(label='Name', Property=(self.group, 'name'))
        hbox1.pack_start(self.txtName.topwidget)
        self.sbGroupIndex = widgets.SpinBtn(label='Group Number', Property=(self.group, 'Index'))
        hbox1.pack_start(self.sbGroupIndex.topwidget)
        vbox.pack_start(hbox1)
        
        hbox2 = widgets.HBox()
        names = ['id', 'Name', 'Num', 'Member', 'cell_active']
        types = [str, str, int, bool, bool]
        order = [2, 3, 1]
        editable = [3]
        l_kwargs = dict(name='Devices', column_names=names, list_types=types, 
                        column_order=order, columns_editable=editable, default_sort_column=2)
        self.device_list = widgets.TreeList(**l_kwargs)
        self.update_device_list()
        self.device_list.connect('cell_toggled', self.on_device_list_cell_toggled)
        hbox2.pack_start(self.device_list.topwidget, expand=True)
        
        l_kwargs.update(dict(name='Groups', column_names=['id', 'Name', 'Num', 'Member', 'cell_active'], 
                             list_types=[str, str, int, bool, bool], column_order=[1, 2, 3], 
                             columns_editable=[2]))
        self.group_list = widgets.TreeList(**l_kwargs)
        self.update_group_list()
        self.group_list.connect('cell_toggled', self.on_group_list_cell_toggled)
        hbox2.pack_start(self.group_list.topwidget, expand=True)
        vbox.pack_start(hbox2, expand=True)
        
        self.topwidget.pack_start(vbox, expand=True)
        
        self.group.bind(member_update=self.on_group_member_update, 
                        property_changed=self.on_group_property_changed)
        self.DeviceSystem.bind(new_device=self.on_ds_new_device)
        self.MainController.bind(new_group=self.on_mc_new_group)
        
    def unlink(self):
        self.group.unbind(self.on_group_member_update, self.on_group_property_changed)
        self.DeviceSystem.unbind(self.on_ds_new_device)
        self.MainController.unbind(self.on_mc_new_group)
        self.txtName.unlink()
        self.sbGroupIndex.unlink()
        super(GroupEditor, self).unlink()
        
    def update_device_list(self, key=None):
        if key:
            devices = {key:self.DeviceSystem.PatchedDevices[key]}
        else:
            devices = self.DeviceSystem.PatchedDevices
        for key, val in devices.iteritems():
            self.device_list.update({key:[val.name, val.device_num, key in self.group.members, True]})
        
    def update_group_list(self, key=None):
        if key:
            groups = {key:self.MainController.Groups[key]}
        else:
            groups = self.MainController.Groups
        for key, val in groups.iteritems():
            if val != self.group and 'MainController_' not in key:
                i = val.Index
                if i is None:
                    i = -1
                self.group_list.update({key:[val.name, i, key in self.group.members, True]})
            
    def on_device_list_cell_toggled(self, **kwargs):
        key = kwargs.get('key')
        device = self.DeviceSystem.PatchedDevices[key]
        if kwargs.get('state'):
            self.group.add_devices(device)
        else:
            self.group.del_member(key)
        
    def on_group_list_cell_toggled(self, **kwargs):
        key = kwargs.get('key')
        group = self.MainController.Groups[key]
        if kwargs.get('state'):
            self.group.add_member(**{key:group})
        else:
            self.group.del_member(key)
            
    def on_group_member_update(self, **kwargs):
        self.update_group_list()
        self.update_device_list()
#        keys = [kwargs.get(key).id for key in ['new', 'deleted'] if key in kwargs]
#        for key in keys:
#            if kwargs.get(key).base_class == 'Group':
#                self.update_group_list(key)
#            else:
#                self.update_device_list(key)

    def on_group_property_changed(self, **kwargs):
        obj = kwargs.get('obj')
        self.update_group_list(obj.id)
        
    def on_ds_new_device(self, **kwargs):
        key = kwargs.get('id')
        if kwargs.get('base') == 'PatchedDevice':
            self.update_device_list(key)
            
    def on_mc_new_group(self, **kwargs):
        key = kwargs.get('id')
        self.update_group_list(key)
